package se.kayarr.cardplay.gameplay;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.pircbotx.Channel;
import org.pircbotx.PircBotX;
import org.pircbotx.User;
import org.pircbotx.hooks.ListenerAdapter;
import org.pircbotx.hooks.events.MessageEvent;
import org.pircbotx.hooks.events.PrivateMessageEvent;

import se.kayarr.cardplay.exceptions.PlayerUnspecifiedException;
import se.kayarr.cardplay.exceptions.PositionOutsideFieldException;
import se.kayarr.cardplay.exceptions.SessionInProgressException;
import se.kayarr.cardplay.exceptions.TooManyPlayersException;
import se.kayarr.cardplay.gameplay.UnitCard.Ability;
import se.kayarr.cardplay.shared.Shared;
import se.kayarr.cardplay.shared.StringReferenceParser;
import se.kayarr.cardplay.shared.StringReferenceParser.NoSuchPlayerException;
import se.kayarr.cardplay.shared.Util;

public class CommandListener extends ListenerAdapter<PircBotX> {
	private Pattern commandPrefix = Pattern.compile("^(?:" + Shared.commandPrefix + "|(.+?)(?:,|:)\\s*)(.+)$");

	@Override
	public void onMessage(MessageEvent<PircBotX> event) throws Exception {
		try {
			ContextFactory.getGlobal().enterContext();
			
			Matcher matcher = commandPrefix.matcher(event.getMessage());
			if(matcher.find()) {
				if(matcher.group(1) == null || event.getBot().getNick().equalsIgnoreCase(matcher.group(1))) {
					handleCommand(event.getBot(), event.getChannel(), event.getUser(), matcher.group(2));
				}
			}
			
			super.onMessage(event);
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		finally {
			Context.exit();
		}
	}
	
	@Override
	public void onPrivateMessage(PrivateMessageEvent<PircBotX> event)
			throws Exception {
		try {
			Matcher matcher = commandPrefix.matcher(event.getMessage());
			if(matcher.find()) {
				if(matcher.group(1) == null || event.getBot().getNick().equalsIgnoreCase(matcher.group(1))) {
					handleCommand(event.getBot(), null, event.getUser(), matcher.group(2));
				}
			}
			else {
				handleCommand(event.getBot(), null, event.getUser(), event.getMessage());
			}
			
			super.onPrivateMessage(event);
		}
		catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public boolean handleCommand(PircBotX bot, Channel chan, User user, String commandLine) {
		String[] commandParts = commandLine.trim().split("\\s+", 2);
		
		switch(commandParts[0].toLowerCase()) {
			case "reload":
				try {
					CardManager.getInstance().reloadCards();
					Type.setupDamageMults();
					
					sendMessage(bot, chan, user, "Reloaded scripts and re-setup damage type multipliers.");
				}
				catch (SessionInProgressException e) {
					sendMessage(bot, chan, user, "You can't reload while a session is in progress.");
				}
				break;
			
			case "rs":
				Session.removeSession();
				SessionChallenge.pendingChallenge = null;
				sendMessage(bot, chan, user, "Removed current session and pending challenge.");
				break;
				
			case "stats": {
				Session session = Session.getCurrentSession();
				
				if(session != null) {
					session.outputStatus();
				}
				else bot.sendNotice(user, "There is currently no ongoing session!");
				
				break;
			}
				
			case "cardinfo":
				if(commandParts.length > 1) {
					Card card = CardManager.getInstance().findCardById(commandParts[1]);
					
					bot.sendNotice(user, card.getDisplayId() + " \"" + card.getName() + "\":");
					bot.sendNotice(user, "  Desc.: " + card.getDesc());
					
					if(card instanceof UnitCard) {
						UnitCard unitCard = (UnitCard) card;
						
						bot.sendNotice(user, "  Max Health: " + unitCard.getMaxHealth());
						bot.sendNotice(user, "  Type: " + unitCard.getType().getName());
						
						bot.sendNotice(user, "  Abilities:");
						int i = 1;
						for(Ability ability : unitCard.getAbilities()) {
							bot.sendNotice(user, "    #" + (i++) + ", \"" + ability.getName() + "\": " + ability.getDesc() +
									" (Can be used on: " + Util.getPrintableTargetFlags(ability.getTargetFlags()) + ")");
						}
					}
					else if(card instanceof ActionCard) {
						ActionCard actionCard = (ActionCard) card;
						bot.sendNotice(user, "  Can target: " + Util.getPrintableTargetFlags(actionCard.getTargetFlags()));
					}
				}
				break;
				
			case "instanceinfo": case "instinfo":
				if(commandParts.length > 1) {
					Session session = Session.getCurrentSession();
					
					try {
						if(session == null) {
							bot.sendNotice(user, "There is no session going on at the moment!");
							break;
						}
						
						Player player = session.getPlayerByDisplayedTitle(user.getNick());
						
						Card.Instance inst = StringReferenceParser.getReferencedInstance(commandParts[1], session, player);
						
						if(inst == null) {
							bot.sendNotice(user, "Unable to find a matching card (or you tried to peek at someone else's cards on their hand)!");
							break;
						}
						
						bot.sendNotice(user, inst + ":");
						bot.sendNotice(user, "  Desc.: " + inst.getCard().getDesc());
						bot.sendNotice(user, "  Owner: " + inst.getOwner().getName());
						
						if(inst instanceof UnitCard.Instance) {
							UnitCard.Instance unit = (UnitCard.Instance) inst;
							
							//bot.sendNotice(user, "  Health: " + unit.getHealth());
							bot.sendNotice(user, "  Type: " + unit.getType().getName());
							
							bot.sendNotice(user, "  Abilities:");
							int i = 1;
							for(Ability ability : unit.getAbilities()) {
								bot.sendNotice(user, "    #" + (i++) + ", \"" + ability.getName() + "\": " + ability.getDesc() +
										" (Can be used on: " + Util.getPrintableTargetFlags(ability.getTargetFlags()) + ")");
							}
						}
						if(inst instanceof ActionCard.Instance) {
							ActionCard.Instance action = (ActionCard.Instance) inst;
							bot.sendNotice(user, "  Can target: " +
									Util.getPrintableTargetFlags(action.getCard().getTargetFlags()));
						}
					}
					catch (NoSuchPlayerException e) {
						bot.sendNotice(user, "There is no player called " + e.getPlayerTitle() + " in this session!");
					}
					catch (TooManyPlayersException e) {
						bot.sendNotice(user, "You can't use @ when there are more than two players in a session!");
					}
					catch (PlayerUnspecifiedException e) {
						bot.sendNotice(user, "If you're not part of the session, you must specify target player!");
					}
					catch (PositionOutsideFieldException e) {
						bot.sendNotice(user, "The position " + e.getX() + ", " + e.getY() +
								" is outside of the specified field!");
					}
				}
				break;
				
			case "session":
				if(commandParts.length > 1) handleSessionCommand(bot, chan, user, commandParts);
				break;
				
			case "command": case "cmd":
				if(commandParts.length > 1) {
					//TODO Once this supports multiple sessions, make it delegate the command handling to the proper IrcPlayer instance
					
					Session session = Session.getCurrentSession();
					
					if(session != null) {
						for(Player p : session.getPlayers()) {
							if(user.getNick().equalsIgnoreCase(p.getName())) {
								p.handleCommandUse(commandParts[1]);
								break;
							}
						}
					}
				}
				break;
			default:
				return false;
		}
		
		return true;
	}
	
	public void handleSessionCommand(PircBotX bot, Channel chan, User user, String[] commandParts) {
		String[] subCommandParts = commandParts[1].split("\\s+", 2);
		
		switch(subCommandParts[0].toLowerCase()) {
			case "challenge": {
				if(Session.getCurrentSession() != null) {
					sendMessage(bot, chan, user, "There is already an ongoing session!");
					return;
				}
				
				if(chan == null) {
					sendMessage(bot, chan, user, "You can only challenge someone from a channel for now!");
					return;
				}
				
				if(SessionChallenge.pendingChallenge != null) {
					sendMessage(bot, chan, user, "There is already a pending challenge!");
					return;
				}
				
				String opponentName = subCommandParts[1];
				
				boolean foundUser = false;
				for(User u : chan.getUsers()) {
					if(u.getNick().equalsIgnoreCase(opponentName)) {
						if(u == bot.getUserBot()) {
							sendMessage(bot, chan, user, "You can't challenge the bot, silly you!");
							return;
						}
						else if(u == user) {
							sendMessage(bot, chan, user, "So you intended to play all by yourself? Won't work!");
							return;
						}
						
						foundUser = true;
						break;
					}
				}
				
				if(!foundUser) {
					sendMessage(bot, chan, user, "There is no one called that in this channel!");
					return;
				}
				
				SessionChallenge.pendingChallenge = new SessionChallenge();
				SessionChallenge.pendingChallenge.challenger = user.getNick();
				SessionChallenge.pendingChallenge.target = opponentName;
				SessionChallenge.pendingChallenge.channel = chan.getName();
				
				sendMessage(bot, chan, user, opponentName + " has been challenged!");
				
				break;
			}
			
			case "accept": {
				if(Session.getCurrentSession() != null) {
					sendMessage(bot, chan, user, "There is already an ongoing session!");
					return;
				}
				
				if(SessionChallenge.pendingChallenge == null ||
						!SessionChallenge.pendingChallenge.target.equalsIgnoreCase(user.getNick())) {
					sendMessage(bot, chan, user, "There is no pending challenge for you!");
					return;
				}
				
				if(chan == null || !chan.getName().equalsIgnoreCase(SessionChallenge.pendingChallenge.channel)) {
					sendMessage(bot, chan, user,
							"You can only accept or decline a challenge from the same channel it was made from for now!");
					return;
				}
				
				sendMessage(bot, chan, user, "The challenge was accepted!");
				
				try {
					Session session = Session.newSession(bot);
					
					session.addPlayer(new Player(session, bot, SessionChallenge.pendingChallenge.challenger));
					session.addPlayer(new Player(session, bot, user.getNick()));
					
					session.addNarrateTarget(SessionChallenge.pendingChallenge.channel);
					
					session.startSession();
					SessionChallenge.pendingChallenge = null;
				}
				catch (SessionInProgressException e) {
					sendMessage(bot, chan, user, "[WTF] A session already in progress.");
				}
				
				break;
			}
			
			case "decline": {
				if(Session.getCurrentSession() != null) {
					sendMessage(bot, chan, user, "There is already an ongoing session!");
					return;
				}
				
				if(SessionChallenge.pendingChallenge == null ||
						!SessionChallenge.pendingChallenge.target.equalsIgnoreCase(user.getNick())) {
					sendMessage(bot, chan, user, "There is no pending challenge for you!");
					return;
				}
				
				if(chan == null || !chan.getName().equalsIgnoreCase(SessionChallenge.pendingChallenge.channel)) {
					sendMessage(bot, chan, user,
							"You can only accept or decline a challenge from the same channel it was made from for now!");
					return;
				}
				
				sendMessage(bot, chan, user, "The challenge was declined...");
				SessionChallenge.pendingChallenge = null;
				
				break;
			}
			
			case "players":
				if(Session.getCurrentSession() == null) {
					bot.sendNotice(user, "There is no session going on at the moment!");
					return;
				}
				
				bot.sendNotice(user, "Players (" + Session.getCurrentSession().getPlayers().size() + "):");
				for(Player player : Session.getCurrentSession().getPlayers()) {
					bot.sendNotice(user, "* " + player);
				}
				break;
				
			default:
				break;
		}
	}
	
	protected void sendMessage(PircBotX bot, Channel chan, User user, String message) {
		bot.sendMessage(chan == null ? user.getNick() : chan.getName(), message);
	}
}